package cn.sylinx.horm.proxy.mapper;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import cn.sylinx.horm.proxy.annotation.PageNumber;
import cn.sylinx.horm.proxy.annotation.PageSize;
import cn.sylinx.horm.proxy.annotation.Param;
import cn.sylinx.horm.proxy.annotation.ParamBean;
import cn.sylinx.horm.util.BeanUtil;
import cn.sylinx.horm.util.Pair;
import cn.sylinx.horm.util.Tuple;

public class MapperMethodMetadata {

    private static final Map<String, MapperMethodMetadata> cachedMethod = new HashMap<>();
    private final Class<?> mapperInterface;
    private final Method method;
    private Annotation[][] parameterAnnotations;
    private Class<?> truelyReturnType;
    private Class<?> returnType;
    // 是否用native sql
    private String metaKey;

    private static String getMetaKey(Class<?> mapperInterface, Method method) {
        return mapperInterface.getName() + "." + method.getName();
    }

    public static MapperMethodMetadata get(Class<?> mapperInterface, Method method) {
        String key = getMetaKey(mapperInterface, method);
        MapperMethodMetadata methodMetadata = get(key);
        if (methodMetadata == null) {
            methodMetadata = setAndGet(key, mapperInterface, method);
        }
        return methodMetadata;
    }

    private static MapperMethodMetadata get(String key) {
        return cachedMethod.get(key);
    }

    private static MapperMethodMetadata setAndGet(String key, Class<?> mapperInterface, Method method) {
        synchronized (key.intern()) {
            MapperMethodMetadata methodMetadata = get(key);
            if (methodMetadata != null) {
                return methodMetadata;
            }
            methodMetadata = new MapperMethodMetadata(mapperInterface, method);
            cachedMethod.put(key, methodMetadata);
            return methodMetadata;
        }
    }

    public MapperMethodMetadata(Class<?> mapperInterface, Method method) {
        this.mapperInterface = mapperInterface;
        this.method = method;
        readMetadata();
    }

    private void readMetadata() {
        // 返回类型
        this.returnType = readReturnType();
        // 获取返回参数
        this.truelyReturnType = readTruelyReturnType();
        // 参数的所有注解
        this.parameterAnnotations = readParameterAnnotations();
        // 元数据唯一标识
        this.metaKey = getMetaKey(mapperInterface, method);
    }

    private Class<?> readReturnType() {
        return method.getReturnType();
    }

    private Annotation[][] readParameterAnnotations() {
        return method.getParameterAnnotations();
    }

    /**
     * 解析参数
     * 
     * @param args
     * @return Tuple[0]:查询参数, Tuple[1]:pageNumber, Tuple[2]:pageSize
     */
    @SuppressWarnings("unchecked")
    public Tuple resove(Object[] args) {
        Integer pageNumber = 0, pageSize = 0;
        // 所有Param参数列表
        List<Pair> paramList = new ArrayList<>();
        // 所有map
        List<Map<String, Object>> mapList = new ArrayList<>();
        // 所有对象类型
        List<Object> beanList = new ArrayList<>();
        for (int i = 0; i < parameterAnnotations.length; ++i) {
            Annotation[] ats = parameterAnnotations[i];
            if (ats != null && ats.length > 0) {
                if (ats[0].annotationType() == PageNumber.class) {
                    pageNumber = (Integer) args[i];
                }
                if (ats[0].annotationType() == PageSize.class) {
                    pageSize = (Integer) args[i];
                }
                if (ats[0].annotationType() == Param.class) {
                    paramList.add(Pair.apply(ats[0], args[i]));
                }
                if (ats[0].annotationType() == ParamBean.class) {
                    beanList.add(args[i]);
                }
            }
            if (args[i] != null && Map.class.isAssignableFrom(args[i].getClass())) {
                mapList.add((Map<String, Object>) args[i]);
            }
        }

        Map<String, Object> ps = new HashMap<>();
        // 遍历Param参数
        paramList.forEach(p -> {
            Param param = p.getObject(0);
            ps.put(param.value(), p.getObject(1));
        });
        // 遍历所有map
        mapList.forEach(ps::putAll);
        // 遍历所有bean
        beanList.forEach(bean -> ps.putAll(BeanUtil.bean2map(bean)));
        // 如果pageNumber,pageSize 为空，则从参数中获取
        if (pageNumber == null || pageNumber < 1) {
            pageNumber = Optional.ofNullable((Integer) ps.get("pageNumber")).orElse(0);
            pageSize = Optional.ofNullable((Integer) ps.get("pageSize")).orElse(0);
        }

        return Tuple.apply(ps, pageNumber, pageSize);
    }

    private Class<?> readTruelyReturnType() {
        Type genericReturnType = method.getGenericReturnType();
        // 获取返回值的泛型参数
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            return (Class<?>) actualTypeArguments[0];
        }
        return Object.class;
    }

    public Class<?> getTruelyReturnType() {
        return truelyReturnType;
    }

    public Class<?> getMapperInterface() {
        return mapperInterface;
    }

    public Method getMethod() {
        return method;
    }

    public String getMethodName() {
        return method.getName();
    }

    public Class<?> getReturnType() {
        return returnType;
    }

    public String getMetaKey() {
        return metaKey;
    }

    public void setMetaKey(String metaKey) {
        this.metaKey = metaKey;
    }
}